package ua.yandex.shad.tempseries;

/**
 * analyzes and predicts temperature by initial set of values.
 * @author Vindex10
 */
public class TemperatureSeriesAnalysis {

  /**
   * values of temperature ordered in time.
   */
  private final double[] temperatureSeries;
  /**
   * minimal permited temperature.
   */
  private final double minTemp = -273;

  /**
   * fills {@link TemperatureSeriesAnalysis#temperatureSeries}
   * with initial values.
   *
   * @param tempSer initial values of temperature;
   */
  public TemperatureSeriesAnalysis(final double[] tempSer)
      throws IllegalTemperatureValueException {
    for (double temp: tempSer) {
      if (temp < minTemp) {
        throw new IllegalTemperatureValueException(minTemp);
      }
    }
    this.temperatureSeries = tempSer;
  }

  /**
   * calculates average temperature through total period of time.
   *
   * @return <code>0</code> if
   * {@link TemperatureSeriesAnalysis#temperatureSeries} is empty;
   *         <code>average temperature</code> if
   * {@link TemperatureSeriesAnalysis#temperatureSeries} has at least 1 element;
   */
  public final double calcAverageTemp() {
    if (temperatureSeries.length == 0) {
      return 0;
    }
    double sum = 0;
    for (double temp : temperatureSeries) {
      sum += temp;
    }
    return sum / temperatureSeries.length;
  }

  /**
   * calculates mean square deviation for
   * {@link TemperatureSeriesAnalysis#temperatureSeries}.
   *
   * @return <code>0</code> if
   * {@link TemperatureSeriesAnalysis#temperatureSeries} is empty;
   *         <code>mean square deviation</code> if
   * {@link TemperatureSeriesAnalysis#temperatureSeries} has at least 1 element;
   */
  public final double calcTempDeviation() {
    if (temperatureSeries.length == 0) {
      return 0;
    }
    double average = this.calcAverageTemp();
    double res = 0;
    for (double temp : temperatureSeries) {
      res += (average - temp) * (average - temp);
    }
    return res / temperatureSeries.length;
  }

  /**
   * finds closest temperature to zero.
   *
   * @return <code>0</code> if
   * {@link TemperatureSeriesAnalysis#temperatureSeries} is empty;
   *         <code>closest to 0 temperature</code> if
   * {@link TemperatureSeriesAnalysis#temperatureSeries} has at least 1 element;
   */
  public final double findTempClosestToZero() {
    return findTempClosestToValue(0);
  }

  /**
   * finds closest temperature to {@code etalon}.
   *
   * @param etalon temperature closest for method finds
   * @return <code>0</code> if
   * {@link TemperatureSeriesAnalysis#temperatureSeries} is empty;
   *         <code>closest to {@code etalon} temperature</code> if
   * {@link TemperatureSeriesAnalysis#temperatureSeries} has at least 1 element;
   */
  public final double findTempClosestToValue(final double etalon) {
    if (temperatureSeries.length == 0) {
      return 0;
    }
    double clTemp = temperatureSeries[0];
    double dev = Math.abs(clTemp - etalon);
    for (double temp : temperatureSeries) {
      if (Math.abs(temp - etalon) <= dev) {
        clTemp = Math.max(temp, clTemp);
        dev = Math.abs(clTemp - etalon);
      }
    }
    return clTemp;
  }

  /**
   * finds temperatures from TemperatureSeriesAnalysis#temperatureSeries}
   * less then {@code etalon}.
   *
   * @param etalon temperature less then method finds
   * @return <code>0</code> if
   * {@link TemperatureSeriesAnalysis#temperatureSeries} is empty;
   *         <code>array of temperatures less then {@code etalon}</code> if
   * {@link TemperatureSeriesAnalysis#temperatureSeries} has at least 1 element;
   */
  public final double[] findTempsLessThen(final double etalon) {
    int count = 0;
    for (double temp : temperatureSeries) {
      if (temp < etalon) {
        count++;
      }
    }
    if (count > 0) {
      double[] lessTemps = new double[count];
      count = 0;
      for (double temp : temperatureSeries) {
        if (temp < etalon) {
          lessTemps[count++] = temp;
        }
      }
      return lessTemps;
    } else {
      double[] lessTemp = new double[1];
      lessTemp[0] = 0;
      return lessTemp;
    }
  }

  /**
   * finds temperatures from TemperatureSeriesAnalysis#temperatureSeries}
   * greater then {@code etalon}.
   *
   * @param etalon temperature greater then method finds
   * @return <code>0</code> if
   * {@link TemperatureSeriesAnalysis#temperatureSeries} is empty;
   *         <code>array of temperatures greater then {@code etalon}</code> if
   * {@link TemperatureSeriesAnalysis#temperatureSeries} has at least 1 element;
   */
  public final double[] findTempsGreaterThen(final double etalon) {
    int count = 0;
    for (double temp : temperatureSeries) {
      if (temp > etalon) {
        count++;
      }
    }
    if (count > 0) {
      double[] lessTemps = new double[count];
      count = 0;
      for (double temp : temperatureSeries) {
        if (temp > etalon) {
          lessTemps[count++] = temp;
        }
      }
      return lessTemps;
    } else {
      double[] lessTemp = new double[1];
      lessTemp[0] = 0;
      return lessTemp;
    }
  }
}
